package org.daochong.lang;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class BeanInfo {
	private Map<String, Method> getMethods;
	private Map<String, Method> setMethods;
	private Map<String, List<Method>> methods;
	private Map<String, Field> fields;
	private Class<?> clazz;
	private String tableName = null;
	private List<String> ids;
	private List<String> transients;
	private Map<String, String> colMapping;
	private List<String> noInsert;
	private List<String> noUpdate;
	private List<String> notNull;
	private Map<String, Integer> lengths;
	private Map<String, Integer> precisions;
	private Map<String, String> sequences;
	private boolean autoCreate;
	private boolean entity;

	public boolean isEntity() {
		return entity;
	}

	public void setEntity(boolean entity) {
		this.entity = entity;
	}

	public BeanInfo(Class<?> clazz) {
		this.clazz = clazz;
		getMethods = new HashMap<String, Method>();
		setMethods = new HashMap<String, Method>();
		methods = new LinkedHashMap<String, List<Method>>();
		fields = new HashMap<String, Field>();
		colMapping = new LinkedHashMap<String, String>();
		transients = new ArrayList<String>();
		noInsert = new ArrayList<String>();
		noUpdate = new ArrayList<String>();
		notNull = new ArrayList<String>();
		lengths = new HashMap<String, Integer>();
		precisions = new HashMap<String, Integer>();
		sequences = new HashMap<String, String>();
		this.tableName = "";
		this.ids = new ArrayList<String>();
		Annotation[] ar = clazz.getAnnotations();
		for (Annotation a : ar) {
			if (a.annotationType().getName().equals("javax.persistence.Entity")
					|| a.annotationType().getName().equals("javax.persistence.Table")) {
				try {
					String str = (String) a.getClass().getDeclaredMethod("name").invoke(a);
					if (StringUtils.isEmpty(this.tableName) && !StringUtils.isEmpty(str)) {
						this.tableName = str;
					}
				} catch (Throwable e) {

				}
				this.entity = true;
			} else if (a.annotationType().getName().equals("org.daochong.lang.annotation.AutoCreate")) {
				this.autoCreate = true;
			}
		}

		Class<?> tmp = clazz;
		while (!tmp.equals(Object.class)) {
			for (Method m : tmp.getDeclaredMethods()) {
				String name = m.getName();
				if ((name.startsWith("get") && !name.equals("getClass")
						|| (name.startsWith("is") && name.length() > 2 && name.charAt(2) >= 64 && name.charAt(2) <= 90))
						&& m.getParameterTypes().length == 0) {
					String n = StringUtils
							.firstLowerCase((name.startsWith("get") ? name.substring(3) : name.substring(2)));
					ar = m.getAnnotations();
					for (Annotation a : ar) {
						if (a.annotationType().getName().equals("javax.persistence.Id")) {
							this.ids.add(n);
						} else if (a.annotationType().getName().equals("javax.persistence.Transient")) {
							this.transients.add(n);
						} else if (a.annotationType().getName().equals("javax.persistence.GeneratedValue")) {
							String value = "";
							Class<?> cls = a.getClass();
							try {
								value = (String) cls.getDeclaredMethod("name").invoke(a);
							} catch (Throwable e) {

							}
							this.sequences.put(n, value);
						} else if (a.annotationType().getName().equals("javax.persistence.Column")) {
							Class<?> cls = a.getClass();
							try {
								String colName = (String) cls.getDeclaredMethod("name").invoke(a);
								if (!StringUtils.isEmpty(colName)) {
									this.colMapping.put(n, colName);
								}
								Boolean b = (Boolean) cls.getDeclaredMethod("nullable").invoke(a);
								if (!b) {
									this.notNull.add(n);
								}
								b = (Boolean) cls.getDeclaredMethod("insertable").invoke(a);
								if (!b) {
									this.noInsert.add(n);
								}
								b = (Boolean) cls.getDeclaredMethod("updatable").invoke(a);
								if (!b) {
									this.noUpdate.add(n);
								}
								int length = (Integer) cls.getDeclaredMethod("length").invoke(a);
								this.lengths.put(n, length);
								int precision = (Integer) cls.getDeclaredMethod("precision").invoke(a);
								this.precisions.put(n, precision);
							} catch (Throwable e) {

							}
						}
					}
					getMethods.put(n, m);

				} else if (name.startsWith("set") && m.getParameterTypes().length == 1) {
					setMethods.put(StringUtils.firstLowerCase(name.substring(3)), m);
				}
				List<Method> list = methods.get(m.getName());
				if (list == null) {
					list = new ArrayList<Method>();
					methods.put(m.getName(), list);
				}
				list.add(m);
			}
			tmp = tmp.getSuperclass();
		}
		for (Field field : clazz.getDeclaredFields()) {
			ar = field.getAnnotations();
			for (Annotation a : ar) {
				if (a.annotationType().getName().equals("javax.persistence.Id")) {
					this.ids.add(field.getName());
				} else if (a.annotationType().getName().equals("javax.persistence.Transient")) {
					this.transients.add(field.getName());
				} else if (a.annotationType().getName().equals("javax.persistence.GeneratedValue")) {
					String value = "";
					Class<?> cls = a.getClass();
					try {
						value = (String) cls.getDeclaredMethod("name").invoke(a);
					} catch (Throwable e) {

					}
					this.sequences.put(field.getName(), value);
				} else if (a.annotationType().getName().equals("javax.persistence.Column")) {
					String n = field.getName();
					Class<?> cls = a.getClass();
					try {
						String colName = (String) cls.getDeclaredMethod("name").invoke(a);
						if (!StringUtils.isEmpty(colName)) {
							this.colMapping.put(n, colName);
						}
						Boolean b = (Boolean) cls.getDeclaredMethod("nullable").invoke(a);
						if (!b) {
							this.notNull.add(n);
						}
						b = (Boolean) cls.getDeclaredMethod("insertable").invoke(a);
						if (!b) {
							this.noInsert.add(n);
						}
						b = (Boolean) cls.getDeclaredMethod("updatable").invoke(a);
						if (!b) {
							this.noUpdate.add(n);
						}
						int length = (Integer) cls.getDeclaredMethod("length").invoke(a);
						this.lengths.put(n, length);
						int precision = (Integer) cls.getDeclaredMethod("precision").invoke(a);
						this.precisions.put(n, precision);
					} catch (Throwable e) {
						e.printStackTrace();
					}
				}
			}
		}
		if (StringUtils.isEmpty(this.tableName)) {
			this.tableName = BeanUtil.toDatabaseCol(clazz.getClass().getSimpleName());
		}
	}

	public boolean isAutoCreate() {
		return autoCreate;
	}

	public Map<String, Integer> getPrecisions() {
		return precisions;
	}

	public Map<String, String> getSequences() {
		return sequences;
	}

	public List<String> getNotNull() {
		return notNull;
	}

	public Map<String, Integer> getLengths() {
		return lengths;
	}

	public List<String> getNoUpdate() {
		return noUpdate;
	}

	public Map<String, String> getColMapping() {
		return colMapping;
	}

	public void setColMapping(Map<String, String> colMapping) {
		this.colMapping = colMapping;
	}

	public String getTableName() {
		return tableName;
	}

	public List<String> getIds() {
		return ids;
	}

	public List<String> getTransients() {
		return transients;
	}

	public Class<?> getClazz() {
		return clazz;
	}

	public List<String> getAllProperties() {
		List<String> list = new ArrayList<String>();
		list.addAll(getMethods.keySet());
		return list;
	}

	public List<Method> getAllPropertiesMethod() {
		List<Method> list = new ArrayList<Method>();
		list.addAll(getMethods.values());
		return list;
	}

	public List<Method> getAllPropertiesMethodSet() {
		List<Method> list = new ArrayList<Method>();
		list.addAll(setMethods.values());
		return list;
	}

	public Method getPropertyGet(String name) {
		String n = name.replaceAll("\\_", "");
		Method m = getMethods.get(n);
		if (m == null) {
			for (String key : getMethods.keySet()) {
				if (key.equalsIgnoreCase(n)) {
					return getMethods.get(key);
				}
			}
		}
		if (m == null && this.colMapping != null && this.colMapping.containsKey(name)) {
			m = this.getMethods.get(this.colMapping.get(name));
		}
		if (m == null && this.colMapping != null) {
			for (String k : this.colMapping.keySet()) {
				if (k.equalsIgnoreCase(name)) {
					return this.getMethods.get(this.colMapping.get(k));
				}
			}
		}
		return m;
	}

	public Field getField(String name) {
		String n = name.replaceAll("\\_", "");
		Field m = this.fields.get(n);
		if (m == null) {
			for (String key : this.fields.keySet()) {
				if (key.equalsIgnoreCase(n)) {
					return this.fields.get(key);
				}
			}
		}
		if (m == null && this.colMapping != null && this.colMapping.containsKey(name)) {
			m = this.fields.get(this.colMapping.get(name));
		}
		if (m == null && this.colMapping != null) {
			for (String k : this.colMapping.keySet()) {
				if (k.equalsIgnoreCase(name)) {
					return this.fields.get(this.colMapping.get(k));
				}
			}
		}
		return m;
	}

	public Method getPropertySet(String name) {
		String n = name.replaceAll("\\_", "");
		Method m = setMethods.get(n);
		if (m == null) {
			for (String key : setMethods.keySet()) {
				if (key.equalsIgnoreCase(n)) {
					return setMethods.get(key);
				}
			}
		}
		return m;
	}

	public List<String> getAllFields() {
		List<String> list = new ArrayList<String>();
		list.addAll(fields.keySet());
		return list;
	}

	public List<Method> getMethods(String name) {
		return methods.get(name);
	}

	public Method getMethod(String name) {
		List<Method> list = methods.get(name);
		if (list != null && list.size() > 0) {
			return list.get(0);
		}
		return null;
	}

	public Method getMethod(String name, Class<?>... cls) {
		List<Method> list = methods.get(name);
		if (list != null && list.size() > 0) {
			for (Method m : list) {
				Class<?>[] ar = m.getParameterTypes();
				if (ar.length == cls.length) {
					boolean find = true;
					for (int i = 0; i < ar.length; i++) {
						if (!ar[i].equals(cls[i])) {
							find = false;
						}
					}
					if (find) {
						return m;
					}
				}
			}
		}
		return null;
	}

	public Method getMethod(String name, Object... objects) {
		List<Method> list = methods.get(name);
		if (list != null && list.size() > 0) {
			for (Method m : list) {
				Class<?>[] ar = m.getParameterTypes();
				if (ar.length == objects.length) {
					boolean find = true;
					for (int i = 0; i < ar.length; i++) {
						if (objects[i] != null && !BeanUtil.isMatch(objects[i], ar[i])) {
							find = false;
							break;
						}
					}
					if (find) {
						return m;
					}
				}
			}
		}
		return null;
	}

	public List<String> getNoInsert() {
		return noInsert;
	}

	public void setNoInsert(List<String> noInsert) {
		this.noInsert = noInsert;
	}

	public void setTableName(String tableName) {
		this.tableName = tableName;
	}

	public void setIds(List<String> ids) {
		this.ids = ids;
	}

	public void setTransients(List<String> transients) {
		this.transients = transients;
	}

	public void setNoUpdate(List<String> noUpdate) {
		this.noUpdate = noUpdate;
	}

	public void setNotNull(List<String> notNull) {
		this.notNull = notNull;
	}

	public void setLengths(Map<String, Integer> lengths) {
		this.lengths = lengths;
	}

	public List<String> getMethodNames() {
		List<String> list = new ArrayList<String>();
		list.addAll(methods.keySet());
		return list;
	}

	public String getColumn(String prop) {
		if (this.colMapping.containsKey(prop)) {
			return this.colMapping.get(prop);
		}
		return prop;
	}

	public void validata(Object o) throws ValidateException {
		for (String prop : this.getAllProperties()) {
			Object param = this.getProperty(prop, o);
			if (param == null && ((this.getNotNull() != null && this.getNotNull().contains(prop))
					|| (this.getIds().contains(prop)))) {
				throw new ValidateException(prop + " Is null");
			}

			if (param != null && param instanceof String && this.getLengths() != null
					&& this.getLengths().containsKey(prop)) {
				int len = this.getLengths().get(prop);
				String str = (String) param;
				if (str.length() > len) {
					throw new ValidateException(prop + " Length > " + len);
				}
			}
		}
	}

	public boolean canPersistence(String prop, Object param) {
		if (!this.getMethods.containsKey(prop)) {
			return false;
		}
		if (param == null && this.getNotNull() != null && this.getNotNull().contains(prop)) {
			return false;
		}
		if (param != null && param instanceof String && this.getLengths() != null
				&& this.getLengths().containsKey(prop)) {
			int len = this.getLengths().get(prop);
			String str = (String) param;
			return str.length() <= len;
		}
		if (this.getTransients().contains(prop)) {
			return false;
		}
		String tmp = prop.replaceAll("\\_", "");
		for (String str : this.getTransients()) {
			if (str.equalsIgnoreCase(tmp)) {
				return false;
			}
		}
		return true;
	}

	public boolean canInsert(String prop, Object param) {
		if (!canPersistence(prop, param)) {
			return false;
		}
		if (this.getNoInsert() != null && this.getNoInsert().contains(prop)) {
			return false;
		}
		if (this.getSequences().containsKey(prop) && StringUtils.isEmpty(this.getSequences().get(prop))) {
			return false;
		}
		return true;
	}

	public boolean canUpdate(String prop, Object param) {
		if (!canPersistence(prop, param)) {
			return false;
		}
		if (this.getNoUpdate() != null && this.getNoUpdate().contains(prop)) {
			return false;
		}
		return true;
	}

	public Object getProperty(String prop, Object o) {
		Method m = this.getPropertyGet(prop);
		if (m != null) {
			try {
				boolean f = m.isAccessible();
				if (!f) {
					m.setAccessible(true);
				}
				Object obj = m.invoke(o);
				if (!f) {
					m.setAccessible(false);
				}
				return obj;
			} catch (Throwable e) {

			}
		}
		Field field = this.getField(prop);
		if (field != null) {
			try {
				boolean f = field.isAccessible();
				if (!f) {
					m.setAccessible(true);
				}
				Object obj = m.invoke(o);
				if (!f) {
					field.setAccessible(false);
				}
				return obj;
			} catch (Throwable e) {

			}
		}
		return null;
	}

	public void setProperty(String prop, Object o, Object value) {
		Method m = this.getPropertySet(prop);
		if (m != null) {
			try {
				boolean f = m.isAccessible();
				if (!f) {
					m.setAccessible(true);
				}
				m.invoke(o, value);
				if (!f) {
					m.setAccessible(false);
				}
				return;
			} catch (Throwable e) {

			}
		}
		Field field = this.getField(prop);
		if (field != null) {
			try {
				boolean f = field.isAccessible();
				if (!f) {
					m.setAccessible(true);
				}
				m.invoke(o, value);
				if (!f) {
					field.setAccessible(false);
				}
			} catch (Throwable e) {

			}
		}
	}
}
